﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Text.RegularExpressions;

namespace GRMS.App_Code
{
    public static class CommonCommands
    {
        #region Regular Expressions
        static string RegexAssign = @"^[A-Za-z0-9]+ = ";
        static string RegexAssignP = @"^[A-Za-z0-9]+ \+= ";
        static string RegexGet = @"^Get: {[A-Za-z0-9]+}";
        static string RegexInt = @"^[1-9][0-9]+";
        static string RegexCompareEqual = @"^[A-Za-z0-9\[\]: \{\}]+ == [A-Za-z0-9\[\]:\{\}\+\- ]+";
        static string RegexCompareNotEqual = @"^[A-Za-z0-9\[\]: \{\}]+ != [A-Za-z0-9\[\]:\{\}\+\- ]+";
        static string RegexCompareGreater = @"^[A-Za-z0-9\[\]: \{\}]+ > [A-Za-z0-9\[\]:\{\}\+\- ]+";
        static string RegexCompareGreaterOrEqual = @"^[A-Za-z0-9\[\]: \{\}]+ >= [A-Za-z0-9\[\]:\{\}\+\- ]+";
        static string RegexCompareLess = @"^[A-Za-z0-9\[\]: \{\}]+< [A-Za-z0-9\[\]:\{\}\+\- ]+";
        static string RegexCompareLessOrEqual = @"^[A-Za-z0-9\[\]: \{\}]+ <=[A-Za-z0-9\[\]:\{\}\+\- ]+";
        static string RegexCompare = @"==|!=|>=|<=|>|<";
        static string RegexOption = @"^[A-Za-z0-9\[\]\{\}: ]+[=!<>A-Za-z0-9\[\]\{\}\+\- ]*: [a-zA-z0-9\{\}\: ]* \|";
        static string RegexOptionSelect = @"^\{[A-Za-z0-9\{\}:\[\]]+[=!<>A-Za-z0-9\[\]\{\}\+\-\\\* ]*\}";
        static string RegexRoll = @"^Roll: [A-Za-z0-9]*d[A-Za-z0-9]+|^Roll: [A-Za-z0-9]*_[A-Za-z0-9]+";
        static string RegexDieValue = @"d[A-Za-z0-9]+";
        static string RegexIsDieValue = @"[A-Za-z0-9]*d[A-Za-z0-9]+|[A-Za-z0-9]*_[A-Za-z0-9]+";
        static string RegexPrintLn = @"PrintLn\(";
        #endregion

        #region Splits and Trims
        static char[] BracketTrim = { '{', '}' };
        static char[] SquareTrim = { '[', ']' };
        static char[] ParensTrim = { '(', ')' };
        static char[] AssignSplit = { '+', '=' };
        static string[] CompareSplit = { "==", "!=", ">=", "<=", ">", "<" };
        static char[] CompareTrim = { ' ', ':' };
        static char[] RollSplit = { '_', 'd', '+' };
        static char[] RollSplitMulti = { '+' };
        static char[] TrimLineBreaks = { '\n', '\r' };
        static char[] MathSplit = { '+', '-', '\\', '*' };
        #endregion

        #region Properties
        static Dictionary<string, string> CustomCommands;
        #endregion

        /// <summary>
        /// Assign a value with a name into the Session
        /// </summary>
        /// <param name="ID">Name</param>
        /// <param name="VALUE">Value</param>
        /// <param name="session">Session to save too</param>
        /// <returns>Successfully assigned</returns>
        public static bool Assignment(string ID, object VALUE, Session session)
        {
            switch(VALUE.GetType().ToString())
            {
                case "System.String":
                    return session.SetGlobal(ID, ((string)VALUE).Trim('\''));
                case "System.Int32":
                    return session.SetGlobal(ID, (int)VALUE);
                default:
                    VALUE = ProcessString(VALUE.ToString(), session);
                    switch (VALUE.GetType().ToString())
                    {
                        case "System.String":
                            return session.SetGlobal(ID, ((string)VALUE).Trim('\''));
                        case "System.Int32":
                            return session.SetGlobal(ID, (int)VALUE);
                        default:
                            return false;
                    }
            }
        }

        public static object Get(string id, Session session)
        {
            return session.GetGlobal(id);
        }

        /// <summary>
        /// Compares too objects to each other.
        /// </summary>
        /// <param name="O1">Left object</param>
        /// <param name="O2">Right object</param>
        /// <returns>1: Less than; 2: Equal; 3: Greater than; -1: Not Equal</returns>
        public static int Comparison(object O1, object O2)
        {
            if (O1.GetType() != O2.GetType())
                return -1;
            else
                switch (O1.GetType().ToString())
                {
                    case "System.String":
                        if (O1.ToString() == O2.ToString())
                            return 2;
                        else
                            return -1;
                    case "System.Int32":
                        if (Int32.Parse(O1.ToString()) == Int32.Parse(O2.ToString()))
                            return 2;
                        else if (Int32.Parse(O1.ToString()) < Int32.Parse(O2.ToString()))
                            return 1;
                        else if (Int32.Parse(O1.ToString()) > Int32.Parse(02.ToString()))
                            return 3;
                        else
                            return -1;
                    default:
                        return -1;
                }
        }

        /// <summary>
        /// Selects an option from list of possiable options.
        /// If list already used, the one used previously will be called.
        /// </summary>
        /// <param name="options">List of options</param>
        /// <param name="choice">Selected Choice</param>
        /// <param name="session">Global State</param>
        /// <returns>Chosen Option</returns>
        public static string Option(string options, string choice, Session session)
        {
            string key = options.Split('|')[0];
            options = options.Replace(key, "").Trim().TrimStart('|').Trim();

            string[] list = options.Split('|');
            Hashtable OPTIONS = new Hashtable();

            if (session.CheckOptionStatements(key))
                OPTIONS = session.GetOptionStatements(key);
            else
                foreach (string s in list)
                {
                    string output = s.Trim().Replace(Regex.Match(s.Trim(), @"^[a-zA-Z0-9]+ {").Value, "").Trim().TrimEnd('}');
                    string result = "";
                    foreach (string str in output.Split('+'))
                        result += ProcessString(str.Trim().Trim('\'').Trim(SquareTrim), session);

                    OPTIONS.Add(s.Trim().Split('{')[0].Trim(), result);
                }

            if (key.Replace(":", "").Trim() != "")
            {
                session.SetOptionStatements(key, OPTIONS);
            }

            if (OPTIONS.ContainsKey(choice))
                return (string)OPTIONS[choice];
            else
                return (string)OPTIONS["default"];
        }
        public static string Option(string options, string choice)
        {
            string key = options.Split('|')[0];
            options = options.Replace(key, "").Trim().TrimStart('|').Trim();

            string[] list = options.Split('|');
            Hashtable OPTIONS = new Hashtable();

            foreach (string s in list)
                OPTIONS.Add(s.Trim().Split('{')[0].Trim(), s.Trim().Split('{')[1].Trim().TrimEnd('}'));

            if (OPTIONS.ContainsKey(choice))
                return (string)OPTIONS[choice];
            else
                return (string)OPTIONS["default"];
        }

        /// <summary>
        /// Returns a die roll between d2 to d100
        /// </summary>
        /// <param name="STEP">Die Step between 1-8</param>
        /// <param name="COUNT">Number of die to roll of selected type</param>
        /// <param name="MOD">Numeric additive for die roll</param>
        /// <returns>Result of the roll</returns>
        public static int Roll(int DIE, int COUNT, int MOD)
        {
            Random r = new Random(DateTime.Now.Millisecond);
            int result = MOD;

            for (int i = 0; i < COUNT; i++)
                result += r.Next(1, DIE);

            return result;
        }

        /// <summary>
        /// Returns value of the Object for printing.
        /// </summary>
        /// <param name="VALUE"></param>
        /// <returns></returns>
        private static string PrintLn(object VALUE, Session current)
        {
            string[] values = VALUE.ToString().Trim('\'').Split('+');
            string result = "";

            foreach (string s in values)
                result += ProcessString(s, current);

            return result + "<br/>";
        }

        public static bool ChangeCharacterValue(string actor, string attribute, object value, bool BASE, Session session)
        {
            Character Actor = new Character(session);
            if (session.HasCharacter(actor))
                Actor = session.GetCharacter(actor);

            if (Actor.HasAttribute(attribute))
            {
                if (BASE)
                    Actor.UpdateBaseProperty(new KeyValuePair<string, string>(attribute, value.ToString()));
                else
                {
                    //ToDo
                }
            }

            return true;
        }

        public static string ProcessString(string s, Session current)
        {
            string line;
            #region Assignment
            if (Regex.Match(s, RegexAssign).Success)
            {
                line = Regex.Match(s, RegexAssign).Value;
                string lineResult = ProcessString(s.Replace(line, "").Trim().Trim('\'').Trim(BracketTrim), current);

                if (Regex.Match(lineResult, "[0-9]+").Success)
                {
                    try
                    {
                        Assignment(line.Split('=')[0].Trim(), Int32.Parse(lineResult), current);
                    }
                    catch (Exception e)
                    {
                        return "Unable to Assign " + line.Split('=')[0].Trim();
                    }
                }
                else
                {
                    try
                    {
                        Assignment(line.Split('=')[0].Trim(), lineResult, current);
                    }
                    catch (Exception e)
                    {
                        return "Unable to Assign " + line.Split('=')[0].Trim();
                    }
                }
                return "";
            }
            else if (Regex.Match(s, RegexAssignP).Success)
            {
                line = Regex.Match(s, RegexAssignP).Value;
                string lineDefault = (current.CheckGlobal(line.Split(AssignSplit)[0].Trim())) ? Get(line.Split(AssignSplit)[0].Trim(), current).ToString() : "";
                string lineResult = ProcessString(s.Replace(line, "").Trim().Trim('\''), current);

                if (Regex.Match(lineResult, "^[0-9]+").Success)
                {
                    try
                    {
                        Assignment(line.Split(AssignSplit)[0].Trim(), Int32.Parse(lineDefault) + Int32.Parse(lineResult), current);
                    }
                    catch (Exception e)
                    {
                        return "Unable to Assign " + line.Split(AssignSplit)[0].Trim();
                    }
                }
                else
                {
                    try
                    {
                        Assignment(line.Split(AssignSplit)[0].Trim(), lineDefault + lineResult, current);
                    }
                    catch (Exception e)
                    {
                        return "Unable to Assign " + line.Split(AssignSplit)[0].Trim();
                    }
                }
                return "";
            }
            #endregion
            #region Option
            else if (Regex.Match(s, RegexOption).Success)
            {
                line = s;
                string choice = Regex.Match(line, RegexOptionSelect).Value.Trim(CompareTrim).Trim(BracketTrim);
                if (choice.Contains("{"))
                {
                    if (Regex.Match(choice, RegexCompare).Success)
                    {
                        string get = Regex.Match(choice, @"^\[.+?\]").Value;
                        choice = choice.Replace(get, ProcessString(get.Trim(SquareTrim), current));
                    }
                    else
                        choice = ProcessString(choice.Trim(SquareTrim), current);
                }

               if ( choice.Contains("==") || choice.Contains("!=") ||
                    choice.Contains("<") || choice.Contains("<=") ||
                    choice.Contains(">") || choice.Contains(">="))
                   choice = ProcessString(choice, current);

                try
                {
                    return Option(line.Replace(Regex.Match(line, RegexOptionSelect).Value, ""), choice, current);
                }
                catch (Exception e)
                {
                    return "Unable to get option for  " + line.Split('=')[0].Trim();
                }
            }
            #endregion
            #region Comparison
            else if (Regex.Match(s, RegexCompareEqual).Success)
            {
                line = Regex.Match(s, RegexCompareEqual).Value;
                try
                {
                    object leftside = new object();
                    object rightside = new object();
                    if (!SplitComparison(line, ref leftside, ref rightside, current))
                        return "false";

                    int lSide = 0;
                    int rSide = 0;
                    if (Int32.TryParse(leftside.ToString(), out lSide) && Int32.TryParse(rightside.ToString(), out rSide))
                    {
                        leftside = lSide;
                        rightside = rSide;
                    }

                    switch (Comparison(leftside, rightside))
                    {
                        case 2:
                            return "true";
                        default:
                            return "false";
                    }
                }
                catch (Exception e)
                {
                    return "Unable to compare " + line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[0].Trim() + " and " + line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[1].Trim();
                }
            }
            else if (Regex.Match(s, RegexCompareNotEqual).Success)
            {
                line = Regex.Match(s, RegexCompareNotEqual).Value;
                try
                {
                    object leftside = new object();
                    object rightside = new object();
                    if (!SplitComparison(line, ref leftside, ref rightside, current))
                        return "false";

                    int lSide = 0;
                    int rSide = 0;
                    if (Int32.TryParse(leftside.ToString(), out lSide) && Int32.TryParse(rightside.ToString(), out rSide))
                    {
                        leftside = lSide;
                        rightside = rSide;
                    }

                    switch (Comparison(leftside, rightside))
                    {
                        case 2:
                            return "false";
                        default:
                            return "true";
                    }
                }
                catch (Exception e)
                {
                    return "Unable to compare " + line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[0].Trim() + " and " + line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[1].Trim();
                }
            }
            else if (Regex.Match(s, RegexCompareGreater).Success)
            {
                line = Regex.Match(s, RegexCompareGreater).Value;
                try
                {
                    object leftside = new object();
                    object rightside = new object();
                    if (!SplitComparison(line, ref leftside, ref rightside, current))
                        return "false";

                    switch (Comparison(Convert.ToInt32(leftside), Convert.ToInt32(rightside)))
                    {
                        case 3:
                            return "true";
                        default:
                            return "false";
                    }
                }
                catch (Exception e)
                {
                    return "Unable to compare " + line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[0].Trim() + " and " + line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[1].Trim();
                }
            }
            else if (Regex.Match(s, RegexCompareGreaterOrEqual).Success)
            {
                line = Regex.Match(s, RegexCompareGreaterOrEqual).Value;
                try
                {
                    object leftside = new object();
                    object rightside = new object();
                    if (!SplitComparison(line, ref leftside, ref rightside, current))
                        return "false";

                    switch (Comparison(Convert.ToInt32(leftside), Convert.ToInt32(rightside)))
                    {
                        case 2:
                            return "true";
                        case 3:
                            return "true";
                        default:
                            return "false";
                    }
                }
                catch (Exception e)
                {
                    return "Unable to compare " + line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[0].Trim() + " and " + line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[1].Trim();
                }
            }
            else if (Regex.Match(s, RegexCompareLess).Success)
            {
                line = Regex.Match(s, RegexCompareLess).Value;
                try
                {
                    object leftside = new object();
                    object rightside = new object();
                    if (!SplitComparison(line, ref leftside, ref rightside, current))
                        return "false";

                    switch (Comparison(Convert.ToInt32(leftside), Convert.ToInt32(rightside)))
                    {
                        case 1:
                            return "true";
                        default:
                            return "false";
                    }
                }
                catch (Exception e)
                {
                    return "Unable to compare " + line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[0].Trim() + " and " + line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[1].Trim();
                }
            }
            else if (Regex.Match(s, RegexCompareLessOrEqual).Success)
            {
                line = Regex.Match(s, RegexCompareLessOrEqual).Value;
                try
                {
                    object leftside = new object();
                    object rightside = new object();
                    if (!SplitComparison(line, ref leftside, ref rightside, current))
                        return "false";

                    switch (Comparison(Convert.ToInt32(leftside), Convert.ToInt32(rightside)))
                    {
                        case 1:
                            return "true";
                        case 2:
                            return "true";
                        default:
                            return "false";
                    }
                }
                catch (Exception e)
                {
                    return "Unable to compare " + line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[0].Trim() + " and " + line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[1].Trim();
                }
            }
            #endregion
            #region Roll
            else if (Regex.Match(s, RegexRoll).Success)
            {
                line = s.Replace("Roll:", "").Trim();
                string[] parse = line.Split(RollSplitMulti);

                try
                {
                    int result = 0;
                    foreach (string die in parse)
                    {
                        string dice = die.Trim();
                        if (dice.StartsWith("d") || dice.StartsWith("_"))
                            dice = "1" + dice;

                        if (current.CheckGlobal(dice))
                        {
                            result += Int32.Parse(Get(dice, current).ToString());
                        }
                        else if (Regex.Match(die.Trim(), RegexIsDieValue).Success)
                        {
                            string[] dieSplit = dice.Split(RollSplit);

                            if (!Regex.Match(dieSplit[0], RegexInt).Success)
                                if (current.CheckGlobal(dieSplit[0].Trim()))
                                {
                                    string rslt = Get(dieSplit[0].Trim(), current).ToString();
                                    dice.Replace(dieSplit[0], rslt);
                                    dieSplit[0] = rslt;
                                }
                            if (!Regex.Match(dieSplit[1], RegexInt).Success)
                            {
                                if (current.CheckGlobal(dieSplit[1].Trim()))
                                {
                                    string rslt = Get(dieSplit[1].Trim(), current).ToString();
                                    dice.Replace(dieSplit[1], rslt);
                                    dieSplit[1] = rslt;
                                }
                            }
                            //else if (Regex.Match(dice, RegexDieValue).Success)
                            //    dieSplit[1] = DieValue2Step(dieSplit[1].Trim());

                            result += Roll(Int32.Parse(dieSplit[1].Trim()) + 1, Int32.Parse(dieSplit[0].Trim()), 0);
                        }
                        else
                        {
                            result += Int32.Parse(die);
                        }
                    }
                    return result.ToString();
                }
                catch (Exception e)
                {
                    return "Unable to roll dice";
                }
            }
            #endregion
            #region Assignment Retrieval
            if (Regex.Match(s, RegexGet).Success)
                return Get(Regex.Match(s, RegexGet).Value.Replace("Get: ", "").Trim(BracketTrim).ToString(), current).ToString();
            #endregion
            #region PrintLn
            if (Regex.Match(s, RegexPrintLn).Success)
            {
                line = s.Replace(Regex.Match(s, RegexPrintLn).Value, "").Trim().Trim(ParensTrim);

                if (line.Contains('+'))
                {
                    string[] lineSplit = line.Split('+');
                    string result = "";
                    foreach (string str in lineSplit)
                        result += ProcessString(str.Trim().Trim('\'').Trim(SquareTrim), current);
                    return PrintLn(result, current);
                }
                else if (line.Contains("Get:"))
                {
                    Regex reg = new Regex(@"\[Get: {[a-zA-Z]+[0-9]*[a-zA-Z]*}\]");
                    string res = ProcessString(reg.Match(line).Value.Trim().Trim('\'').Trim(SquareTrim), current);
                    return line.Replace(reg.Match(line).Value, res);
                }
                else
                {
                    return PrintLn(s.Replace("PrintLn(", "").Trim(ParensTrim).Trim('\'').Trim(), current);
                }
            }
            #endregion
            #region Change Character Property
            //ToDo
            #endregion

            return s;
        }

        private static bool SplitComparison(string line, ref object leftside, ref object rightside, Session current)
        {
            string leftStr = line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[0].Trim();
            if (current.CheckGlobal(leftStr.ToString()))
                leftStr = Get(leftStr, current).ToString();
            else
                leftStr = ProcessString(leftStr, current);
            if (Regex.Match(leftStr, @"\+|\-").Success)
            {
                string[] math = leftStr.Split(' ');
                switch (math[1])
                {
                    case "+":
                        leftStr = (Int32.Parse(math[0]) + Int32.Parse(math[2])).ToString();
                        break;
                    case "-":
                        leftStr = (Int32.Parse(math[0]) - Int32.Parse(math[2])).ToString();
                        break;
                    default:
                        break;
                }
            }

            string rightStr = line.Split(CompareSplit, StringSplitOptions.RemoveEmptyEntries)[1].Trim();
            if (current.CheckGlobal(rightStr.ToString()))
                rightStr = Get(rightStr.ToString(), current).ToString();
            else
                rightStr = ProcessString(rightStr.ToString(), current);
            if (Regex.Match(rightStr, @"\+|\-").Success)
            {
                string[] math = rightStr.Split(' ');
                switch (math[1])
                {
                    case "+":
                        rightStr = (Int32.Parse(math[0]) + Int32.Parse(math[2])).ToString();
                        break;
                    case "-":
                        rightStr = (Int32.Parse(math[0]) - Int32.Parse(math[2])).ToString();
                        break;
                    default:
                        break;
                }
            }

            leftside = leftStr;
            rightside = rightStr;
            return true;
        }

        private static string ProcessCustomCommand(string s, Session current)
        {
            string command = GetCustomCommand(s, current);

            return "No Custom Commands Defined";
        }

        private static bool BuildCustomCommands(Session current)
        {
            CustomCommands = new Dictionary<string, string>();
            Dictionary<string, Hashtable> results = XMLReader.GetValuesFrom(@"\App_Data\Rules\Rules.xml", "rules stmts stmt", current);

            foreach (KeyValuePair<string, Hashtable> kvp in results)
            {
                CustomCommands.Add(kvp.Key, kvp.Value["value"].ToString());
            }
            return true;
        }

        public static string GetCustomCommand(string s, Session current)
        {
            if (CustomCommands == null)
                BuildCustomCommands(current);

            if (CustomCommands.ContainsKey(s))
            {
                return CustomCommands[s];
            }

            return "Custom Command Not Defined";
        }

        public static List<string> GetCustomCommandList(Session current)
        {
            if (CustomCommands == null)
                BuildCustomCommands(current);

            List<string> commands = new List<string>();
            foreach (string key in CustomCommands.Keys)
                commands.Add(key);

            return commands;
        }
    }
}
